#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   Package
@Time    :   2023/03/16 23:15:12
@author  :   Eval
@Version :   1.0
@Describe:   背包管理
'''
from pygame.locals import *
from lib.scripts.GC import *
from lib.scripts.ui.ShowDiaLog import *
import sys

class Package:
    def __init__(self,parent) -> None:
        import main
        self.parent:main.Run = parent
        self.packBG = pygame.Surface((400,400))
        self.packBG.set_alpha(170)
        self.__isActive = False # 背包是否激活, 禁止从外部直接修改

        self.currIndex = -1  # 当前鼠标悬浮的坐标
        self.currPos = [0,0]
        self.boxSize = 40
        # 玩家背包, 展示渲染的道具,容量有限
        self.__package:list[Item] = [None] * 32
        # 玩家背包, 不渲染在背包的道具, 容量无限, 使用字段存储
        self.__packageHide:dict[Item] = {}
        # 当前需要展示的道具详情信息
        self.itemText = {}
        # 悬浮方框
        self.item_cursor = pygame.image.load(r"lib\images\item_cursor.png").convert_alpha()
        self.item_cursor = pygame.transform.scale(self.item_cursor,(80,40))
        # 格子背景
        self.itemPag = pygame.image.load(r"lib\images\itemPag.png").convert_alpha()
        self.itemPag = pygame.transform.scale(self.itemPag,(40,40))

        self.item_selected = pygame.image.load(r"lib\images\item_selected.png").convert_alpha()
        self.item_selected = pygame.transform.scale(self.item_selected,(40,40))
        # 创建按钮组
        self.Btn = pygame.image.load(r"lib\images\btn.png").convert_alpha()
        self.Btn = pygame.transform.scale(self.Btn,(60,30))
        # 按钮触发激活之后的样式
        self.BtnPre = self.Btn.copy()
        self.BtnPre.set_alpha(200)
        self.BtnGroups = {
            "sortBag":{
                "name":"整理",
                "img":self.Btn.copy(),
                "rect":self.Btn.get_rect(x=560,y=255),
                "function":self.__sortBag,
                "active":False
            },
            "exitBag":{
                "name":"关闭",
                "img":self.Btn.copy(),
                "rect":self.Btn.get_rect(x=560,y=300),
                "function":self.ShowUI,
                "active":False
            }
        }

        # 背包格子偏移量
        self.offX = 230
        self.offY = 255

        self.mousepre = False  # 当前是否按下鼠标
        self.mouseMov = False  # 当前是否移动鼠标
        self.mouseMVPos = [0,0]
        self.mouseMask = pygame.Surface((40,40))
        self.mouseMask.set_alpha(200)

        # 道具权重,背包排序使用
        self.itemWeigt ={
            "constants":9,
            "equips":5,
            "tools":3,
            "items":7
        }

    def render(self):
        """渲染背包"""
        if not self.isActive():
            return
        self.render_view()
        # 监听事件
        self.events()

    def render_view(self):
        # 背包页面主体背景
        self.parent.win.blit(self.packBG,(225,25))
        # 分割线
        pygame.draw.aaline(self.parent.win,(200,200,200),(225,240),(625,240))
        # 背包边框
        pygame.draw.rect(self.parent.win,(200,200,0),(225,25,400,400),width=2)
        # 按钮组
        for btn in self.BtnGroups:
            if self.BtnGroups[btn]["active"]:
                self.parent.win.blit(self.BtnPre,self.BtnGroups[btn]["rect"])
                self.parent.GameFont.render_lineText(self.BtnGroups[btn]["rect"][0]+18,self.BtnGroups[btn]["rect"][1]+5,text=self.BtnGroups[btn]["name"],color=(150,150,150),fontSize=14)
            else:
                self.parent.win.blit(self.BtnGroups[btn]["img"],self.BtnGroups[btn]["rect"])
                self.parent.GameFont.render_lineText(self.BtnGroups[btn]["rect"][0]+18,self.BtnGroups[btn]["rect"][1]+5,text=self.BtnGroups[btn]["name"],color=(255,210,255),fontSize=14)

        boxIn = 0
        boxY = 0
        # 背包格子边框
        pygame.draw.rect(self.parent.win,(200,200,200),(self.offX-2,self.offY-2,self.boxSize*8+5,self.boxSize*4+5),width=1)
        for i in range(32):
            bx = self.offX+boxIn*self.boxSize
            by = self.offY+boxY*self.boxSize
            # 背包格子背景
            self.parent.win.blit(self.itemPag,(bx,by))
            if i == self.currIndex:
                # pygame.draw.rect(self.parent.win,(200,200,0),(bx,by,self.boxSize,self.boxSize),width=2)
                if self.parent.GInit.delay <= 50:
                    self.parent.win.blit(self.item_cursor,(bx,by),(40,0,40,40))
                else:
                    self.parent.win.blit(self.item_cursor,(bx,by),(0,0,40,40))
                
            if not self.__package[i] is None:
                self.parent.win.blit(self.__package[i].icon,(bx+5,by+5),(0,self.__package[i].imgIndex*32,32,32))
                # 如果道具的数量大于1,那么渲染道具数量
                if int(self.__package[i].number) > 1:
                    self.parent.GameFont.render_lineText(bx+3,by+25,self.__package[i].number,color=(255,210,255),fontSize=10)
            boxIn += 1
            if (i+1) % 8 ==0:
                boxIn = 0
                boxY += 1
        
        # 将拖拽时显示的遮罩放在前面, 防止将选中框挡住, 道具的推拽预览优先级最高
        if self.mouseMov:
            # 在被选中拖动的道具格子上方渲染一个暗色的 遮罩层,表明已经被选中了
            self.parent.win.blit(self.mouseMask,self.currPos)

        if len(self.itemText):
            # 如果单击了道具， 那么显示选中框
            self.parent.win.blit(self.item_selected,self.currPos)
            self.parent.win.blit(self.itemPag,(230,30))
            self.parent.win.blit(self.itemText["image"],(235,35),(0,self.itemText["index"]*32,32,32))
            self.parent.GameFont.render_lineText(280,30,self.itemText["name"],color=(255,210,0),fontSize=14)  # 道具名称
            self.parent.GameFont.render_lineText(280,50,self.itemText["num"],color=(124,252,0),fontSize=12)  # 道具数量
            self.parent.GameFont.render_lineText(350,50,self.itemText["type"],color=(238,0,238),fontSize=12)  # 道具类型
            self.parent.GameFont.render_multilineText(230,80,self.itemText["des"],382,fontSize=14)  # 道具描述

        if self.mouseMov:
            self.parent.win.blit(self.itemText["image"],(self.mouseMVPos[0]-15,self.mouseMVPos[1]-15),(0,self.itemText["index"]*32,32,32))


    def events(self):
        pos = pygame.mouse.get_pos()
        # 装备格子区域
        if pos[0] >= self.offX and pos[0] < self.offX+320 and pos[1] >= self.offY and pos[1] < self.offY+160:
            px = (pos[0] - self.offX) // self.boxSize 
            py = (pos[1] - self.offY) // self.boxSize
            self.currIndex = px+(py+7*py)
        else:
            self.currIndex = -1

        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif e.type == pygame.MOUSEBUTTONDOWN:
                if pygame.mouse.get_pressed() == (1,0,0):
                    self.mousepre = True
                    if pos[0] >= self.offX and pos[0] < self.offX+320 and pos[1] >= self.offY and pos[1] < self.offY+160:
                        self.currPos = [px*self.boxSize+self.offX ,py*self.boxSize+self.offY ]
                        item = self.__package[self.currIndex]
                        # print(item)
                        if not item is None:
                            self.itemText = {
                                "name":item.name,
                                "num":"数量:"+str(item.number),
                                "des":item.describe,
                                "image":item.icon,
                                "index":item.imgIndex,
                                "type":GC.GC_IType[item.type]
                            }
                            return
                    else:
                        for btn in self.BtnGroups:
                            if pos[0] >= self.BtnGroups[btn]["rect"].left and pos[0] < self.BtnGroups[btn]["rect"].right and \
                                pos[1] >= self.BtnGroups[btn]["rect"].top and pos[1] < self.BtnGroups[btn]["rect"].bottom:
                                self.BtnGroups[btn]["active"] = True
                                if self.BtnGroups[btn]["function"] is not None:
                                    # 执行按钮事件
                                    self.parent.GameMusic.PlaySound("click.mp3")
                                    self.BtnGroups[btn]["function"]()
                                    break
                    self.itemText.clear()

                # 鼠标右键
                elif pygame.mouse.get_pressed() == (0,0,1):
                    if pos[0] >= self.offX and pos[0] < self.offX+320 and pos[1] >= self.offY and pos[1] < self.offY+160:
                        item = self.__package[self.currIndex]
                        if not item is None:
                            # 根据取到的道具id, 使用道具
                            self.rmPackItemID(item.id,callScript=True)

            elif e.type == pygame.MOUSEMOTION:
                # 鼠标移动
                self.mouseMov = False
                # 如果鼠标处于点击状态,, 且有道具格子被选中了, 那么才会执行道具拖拽事件
                if self.mousepre:
                    if self.itemText.get("name"):
                        self.mouseMov = True
                        self.mouseMVPos = pos

            elif e.type == pygame.MOUSEBUTTONUP:
                # 松开鼠标时,如果拖拽事件被激活,且没有拖拽到背包容器格子区域外部, 那么执行交换
                if self.mouseMov and self.currIndex != -1:
                    px = (self.currPos[0] - self.offX) // self.boxSize 
                    py = (self.currPos[1] - self.offY) // self.boxSize
                    oldPos = px+(py+7*py)
                    self.exChangeItem(oldPos,self.currIndex)
                    # 播放道具交换音效
                    self.parent.GameMusic.PlaySound("pickItem.mp3")
                self.mousepre = False
                self.mouseMov = False
                for btn in self.BtnGroups:
                    if pos[0] >= self.BtnGroups[btn]["rect"].left and pos[0] < self.BtnGroups[btn]["rect"].right and \
                        pos[1] >= self.BtnGroups[btn]["rect"].top and pos[1] < self.BtnGroups[btn]["rect"].bottom:
                        self.BtnGroups[btn]["active"] = False
                        break
                
                
            elif e.type == pygame.KEYDOWN:
                if e.key == K_b or e.key == K_ESCAPE:
                    self.ShowUI()


    def addItem(self,sKey:str,number:int=1):
        """获得道具,根据道具Key 增加到背包"""
        if int(number) <= 0 or int(number) >= 9999:
            # ShowDiaLog.showTips(f"道具数量不合法")
            print_error(f"添加道具:{sKey} 的数量不合法")
            return
        item = Item(str(sKey),int(number))
        if item.id is None:
            ShowDiaLog.showTips(f"未知道具:{sKey}")
            return 
        # 如果道具是不显示在背包中的, 那么加入到 self.__packageHide 中 ,
        if item.showBag:
            ShowDiaLog.showTips(f"获得道具:{item.name}x{number}")
            if not self.__packageHide.get(str(item.sKey)) is None:
                self.__packageHide[str(item.sKey)].number =  int(self.__packageHide[str(sKey)].number)+ int(item.number)
                return
            self.__packageHide[str(item.sKey)] =  item
            return

        for i in range(len(self.__package)):
            # 将道具添加到空格子
            if self.__package[i] is None:
                self.__package[i] = item
                ShowDiaLog.showTips(f"获得道具:{item.name}x{number}")
                return
        ShowDiaLog.showTips("背包满咯")

        
    def addItemByName(self,sName:str,number:int=1):
        """获得道具, 根据道具名称增加到背包"""
        itemKey:str = None
        for sItem in GC.GC_SourceMgr:
            if GC.GC_SourceMgr[sItem].get("name") == sName:
                itemKey = sItem  # 取到的值为字典
                break
        # print(f"itemKey: {itemKey}")
        self.addItem(itemKey,number)
            
    def isActive(self):
        """返回背包是否激活状态"""
        return self.__isActive

    def ShowUI(self,active=None):
        """关闭背包"""
        if active is not None:
            self.__isActive = not active
        self.BtnGroups["exitBag"]["active"] = False  # 将退出按钮的激活状态取消,背包直接关闭之后,没法继续循环渲染,
        self.__isActive = not self.__isActive
        self.currIndex = -1
        self.itemText.clear()

    def getByPackItemKey(self,sKey:str):
        """
        @param {*}: 道具的 key
        获取到背包里的道具,使用 Key当关键字,
        @Return Item
        """
        if sKey is None:
            return sKey
        item = self.__packageHide.get(str(sKey))
        if item is None:
            # 如果不在隐藏背包内, 那么再次判断是否在 可渲染的背包内
            for pItem in self.__package:
                if pItem is not None and pItem.sKey == sKey:
                    item = pItem
                    break
        return item
            
    def getByPackItemID(self,id:str):
        """
        @param {*}: 道具的独立 id
        获取到背包里的道具,使用 id 当关键字,
        @Return Item
        """
        if id is None:
            return id
        for hItem in self.__packageHide:
            if self.__packageHide[hItem].id == str(id):
                return self.__packageHide[hItem]
        
        # 如果不在隐藏背包, 那么就遍历可视化背包
        for pItem in self.__package:
            if pItem is not None and pItem.id == id:
                return pItem
        return None
    
    def getByPackItemName(self,sName:str):
        """
        @@param {*}: 道具的 名称
        获取到背包里的道具, 根据道具的名称获取
        @Return Item
        """
        if sName is None:
            return sName
        for pItem in self.__package:
            if pItem and pItem.name == sName:
                return pItem
            
        for sKey in self.__packageHide:
            item = self.__packageHide.get(sKey)
            if item and item.name == sName:
                return item
            
        return None

    
    def rmPackItemID(self,id:str=None,number:int=1,callScript=True):
        """扣除道具,接收的参数为每个道具独立的id,"""
        return self.__rmPackItem(self.getByPackItemID(id),number,callScript)

    def rmPackItemKey(self,sKey:str=None,number:int=1,callScript=True):
        """扣除道具,接收的参数为每个道具资源的key,"""
        # 判断这个道具是否在隐藏背包
        return self.__rmPackItem(self.getByPackItemKey(sKey),number,callScript)
    
    def rmPackItemName(self,sName:str=None,number:int=1,callScript=True):
        """扣除道具,接收的参数为 道具的名称,"""
        # 判断这个道具是否在隐藏背包
        return self.__rmPackItem(self.getByPackItemName(sName),number,callScript)
    

    def __rmPackItem(self,item,number:int=1,callScript=True):
        """ 仅背包内部可调用,"""
        from lib.scripts.Package import Item
        # 显式声明参数类型
        item:Item = item
        # 判断这个道具是否在隐藏背包
        if item is None:
            ShowDiaLog.showTips("不存在的道具")
            return False
        if int(item.number) < number:
            ShowDiaLog.showTips("道具数量不足")
            return False
        
        # 是否在扣除道具时自动触发脚本,默认为 True, 如果是通过 道具位置交换等方法触发时, 将 callScript 设置为 False
        if callScript:
            # 如果道具有脚本,那么指向脚本
            GC.useScript(item.script,args=[self.parent,item])

        # 判断类型是 items 和tools才扣除
        if item.type == "constants" or item.type == "equips":
            return True
        item.number = int(item.number) - number
        if int(item.number) == 0:
            # 获取到道具在可视化背包的索引,
            hitem = self.__packageHide.get(str(item.sKey))
            # 判断是从哪个背包里面取出来的
            if hitem is None:
                index = self.__package.index(item)
                # 从可视背包中取出来的, 道具数量为0时不能直接从背包中删除, 需要将这个索引的位置 重置为 None
                self.__package[index] = None
        if callScript:
            # ShowDiaLog.showTips(f"使用道具:{item.name}成功!")
            print_info(f"使用道具:{item.name}成功!")
        return True

    def __sortBag(self):
        """整理背包"""
        def sort(item:Item):
            """
            背包排序规则: 根据道具类型的权重排序
            """
            if item is None:
                return 0
            return int(self.itemWeigt[item.type] * 100) - int(item.number)
        
        self.__package.sort(key=sort, reverse=True)

    def exChangeItem(self,oldBox:int,newBox:int):
        """交换道具格子"""
        # 如果格子位置没有发生变化,那么跳出
        if oldBox == newBox:
            return
        # 如果新格子的空的, 或者是两种不同类型的道具,那么直接交换
        if self.__package[newBox] is None or self.__package[oldBox].sKey != self.__package[newBox].sKey:
            self.__package[oldBox],self.__package[newBox] = self.__package[newBox],self.__package[oldBox]
            return
        
        # 相同的道具判断是否允许叠加, (是否达到了叠加上限,如果不允许叠加,那么执行交换)
        if self.__package[newBox].number == self.__package[newBox].maxNum:
            self.__package[oldBox],self.__package[newBox] = self.__package[newBox],self.__package[oldBox]
            return
        
        # 扣除原本的道具
        # 如果允许叠加,那么判断还差多少才会达到上限,取得扣除差值
        if self.__package[oldBox].number + self.__package[newBox].number > self.__package[newBox].maxNum:
            self.rmPackItemID(self.__package[oldBox].id,self.__package[newBox].maxNum - self.__package[newBox].number,callScript=False)
            # 新格子的道具直接加满
            self.__package[newBox].number = self.__package[newBox].maxNum
        else:
            # 将旧格子道具的数量全部加到新格子上
            self.__package[newBox].number += self.__package[oldBox].number
            # 旧格子道具直接扣光
            self.rmPackItemID(self.__package[oldBox].id,self.__package[oldBox].number,callScript=False)

class Item:
    def __init__(self,sKey:str,number:int=1) -> None:
        self.uid:str = None # 资源配置里保存的id , 供游戏其他相关功能查找时匹配
        self.id:str = None  # 道具独立的id, 使用uuid生成
        self.name:str = ""
        self.nName:str = ""  # 道具的别名,无特殊需求的话一般为空
        self.sKey:int = None  # 对应的资源配置 key, 供游戏其他相关功能查找时匹配
        self.describe:str = ""  # 道具描述
        self.showBag:bool = True  # 是否显示在背包ui,  默认都是True
        self.equipData:dict = None  # 如果是装备,  装备的数据
        self.imgIndex:int = 0  # 当前道具处于整体素材的索引

        self.icon:pygame.Surface = None  # 显示道具的图标
        self.images:pygame.Surface = None  # 显示道具详情时的 大图
        self.money:int = 0  # 道具的价值
        self.type = None  # 道具的类型, 是永久道具还是消耗道具, 装备
        self.number = 0  # 创建时初始的数量
        self.maxNum = 0   # 道具叠加的上限, 装备不可叠加, 仅消耗类型的道具可叠加
        self.value = 0   # 触发脚本事件时,具体执行的值
        self.script = None  # 道具需要挂载 的脚本

        self.__load(sKey,number)

    def __load(self,sKey:str,number):
        """根据传进来的 key判断类型是否是 items, 如果不是, 不予创建"""
        data:dict = GC.GC_SourceMgr.get(str(sKey))
        if data is None or data.get("id") is None or data["type"] != "items":
            return None
        self.uid = data["id"]
        self.id = str(uuid4())
        self.name = data["name"]
        self.sKey = str(sKey)
        self.describe = data.get("describe") or "null"
        self.showBag = data["showBag"]
        self.equipData = data["equipData"]
        self.maxNum = int(data.get("maxNum")) or 1
        self.type = data.get("IType") or "items"
        self.money =  data.get("money") or 1
        self.value =  data.get("value") or 0
        self.script =  data.get("script")
        self.imgIndex = data["index"]
        if self.type == "constants" or self.type == "equips":
            self.maxNum = 1
        self.number = self.maxNum if int(number) >= int(self.maxNum) else int(number)
        
        self.icon = GC.GC_GameSurfaceCache.get(data["type"])
    
    def __str__(self) -> str:
        return f"名称:{self.name},key:{self.sKey},数量:{self.number},类型:{self.type},id:{self.id}"
    